package appml

import (
	"io"
	"os"
	"path/filepath"
	"runtime"
	"slices"
	"strings"
)

// 清理 cmd 的参数，删除首末的引号与空白
func CleanCmdArg(arg string) string {
	ret := os.ExpandEnv(strings.TrimSpace(arg)) // 删除首末空白
	if len(ret) > 0 && (ret[0] == '"' || ret[0] == '\'') {
		// 只有以 " 或 ' 开始的，才删除引号。
		// 例如 name="a b c" 则不删除。
		// 参数值中间如有转义，如 \" 则不处理。
		return strings.TrimSpace(strings.Trim(ret, `"'`))
	}
	return ret
}

// 解析命令行参数。
// 例如输入 cmdline 为 "\"path/to/exe\" arg1 arg2" 返回 ["path/to/exe", "arg1", "arg2"]
func ParseCmdLine(cmdline string) (args []string, err error) {
	var last, quote rune
	var arg strings.Builder

	args = make([]string, 0)
	for _, char := range strings.TrimSpace(cmdline) {
		switch char {
		case '"', '\'': // 双引号/单引号
			// 	注：只处理两端的引号，中间的转义不处理
			if quote == 0 && last != '\\' {
				quote = char // 引号开始，可以是 "abc" 或 a="bc" 形式
			} else if char == quote && last != '\\' {
				quote = 0 // 引号结束
			}
			arg.WriteRune(char) // 包括引号在内，作为一个完整的参数
		case ' ', '\t':
			if quote == 0 { // 空白不在引号内，则作为参数的结束符，并忽略空白字符本身
				if arg.Len() > 0 {
					args = append(args, CleanCmdArg(arg.String()))
					arg.Reset()
				}
			} else { // 空白在引号内，则作为参数的一部分，情形："ab c"
				arg.WriteRune(char)
			}
		default:
			arg.WriteRune(char)
		}
		last = char
	}
	// 处理最后一个参数
	if arg.Len() > 0 {
		args = append(args, CleanCmdArg(arg.String()))
	}
	return
}

// 原始的删除函数
func origin_delete[K comparable, V any](m map[K]V, k K) { delete(m, k) }

// 删除符合条件的 map 元素。当 filter 返回 true 时，该元素将会被删除。
// 如果 filter 返回 error，则不做任何删除操作，并立即通过 MapDelete 返回该 error。
func MapDelete[K comparable, V any](m map[K]V, filter func(k K, v V) (bool, error), del func(m map[K]V, k K)) error {
	gosts := make([]K, 0, len(m))
	for k, v := range m {
		if match, err := filter(k, v); err != nil {
			return err
		} else if match {
			gosts = append(gosts, k)
		}
	}
	if del == nil {
		del = origin_delete
	}
	for _, k := range gosts {
		del(m, k)
	}
	return nil
}

// 清理相对路径：对路径标准化之后（分隔符为 / ），再删除前缀 "./" ，最后删除两端的 "/" 。
// 如果清理之后的最终结果为 "."，则返回 ""。
func CleanRelatedPath(path string) string {
	ret := strings.Trim(strings.TrimPrefix(filepath.ToSlash(filepath.Clean(path)), "./"), "/")
	if ret == "." {
		return ""
	}
	return ret
}

// 是否在 paths 中包含 path 目录，符合以下条件之一，表示包含：
//
// - paths 为空或 paths[0] 为 "*"|"/"|"/*"
//
// - paths 包含 path
//
// - paths 包含 path/*
func IncludePath(path string, paths []string) bool {
	if path == "." || path == "" {
		return false
	}
	if len(paths) == 0 || paths[0] == "*" || paths[0] == "/" || paths[0] == "/*" {
		return true
	}
	if slices.Contains(paths, path) || slices.Contains(paths, path+"/*") {
		return true
	}
	return IncludePath(filepath.ToSlash(filepath.Dir(path)), paths)
}

// 数组映射
func SliceMapper[F any, T any](s []F, fn func(F) (T, bool)) []T {
	ret := make([]T, 0, len(s))
	for _, v := range s {
		if r, ok := fn(v); ok {
			ret = append(ret, r)
		}
	}
	return ret
}

// 过滤空字符串
func SliceFilterEmptyFunc(s []string, fn func(string) string) []string {
	return SliceMapper(s, func(v string) (string, bool) {
		vv := fn(v)
		return vv, vv != ""
	})
}

// 判断目录是否为空
func DirIsEmpty(dir string) bool {
	fd, err := os.Open(dir)
	if err != nil {
		return os.IsNotExist(err)
	}
	defer fd.Close()
	_, err = fd.Readdirnames(1)
	return err == io.EOF
}

// 删除 root 里面的 dir 及其父的空目录
func DirRemoveEmpties(dir, root string) error {
	if len(dir) <= len(root) || !strings.HasPrefix(dir, root) || !DirIsEmpty(dir) {
		return nil // 要删除的目录比根目录还短，则忽略；非空目录，返回。
	}
	if err := os.Remove(dir); err != nil {
		return NewErr(runtime.Caller(0)).Err(err).Msg("remove %s faild,", dir)
	}
	return DirRemoveEmpties(filepath.Dir(dir), root)
}
